﻿using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using ObservableCollections.Events;
using ObservableCollections.Interfaces;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.ServiceModel.DomainServices.Client;
using ObservableCollections.Extensions;
using System.Runtime.Serialization;

namespace ObservableCollections
{
    [DataContract]
    public class ObservableEntityCollection<T> : ObservableCollection<T>
        where T : Entity
    {
       
        private readonly EntityCollection<T> _entityCollection;
        private readonly T _entity;
        private bool _isSingle = false;
        private readonly bool _pushDeleteOperations;
        private readonly List<Entity> _deletedItems = new List<Entity>();
        private readonly List<Entity> _addedItems = new List<Entity>();
        private readonly ICollectionChanged _parent;

        public event DuplicateEventHandler<T> DuplicateChanged;
 
        public ObservableEntityCollection(EntityCollection<T> entityCollection, ICollectionChanged parent, bool pushDeleteOperations = false)
        {
            if (parent == null) return;
            _parent = parent;
            _entityCollection = entityCollection;
            _pushDeleteOperations = pushDeleteOperations;
            foreach (var i in entityCollection)
            {
                this.Items.Add(i);
            }
            this.CollectionChanged += ObservableEntityCollectionCollectionChanged;
            this._entityCollection.EntityAdded += EntityCollectionEntityAdded;
            this._entityCollection.EntityRemoved += EntityCollectionEntityRemoved;
            foreach (var item in Items)
            {
                ObserveItemChanges(item);
            }
        }

        public ObservableEntityCollection(T entity, ICollectionChanged parent, bool pushDeleteOperations = false)
        {
            if (parent == null) return;
            _parent = parent;
            _pushDeleteOperations = pushDeleteOperations;
            _entity = entity;
            _isSingle = true;
            if (entity != null)
            {
                this.Items.Add(entity);
                ObserveItemChanges(entity);
            }
            this.CollectionChanged += ObservableEntityCollectionCollectionChanged;
        }

        private void ObserveItemChanges(Entity e)
        {
            if (e == null) return;
            CheckDuplicates(e);
            if(e is IDuplicateAwareEntity)
            {
                (e as IDuplicateAwareEntity).DuplicateChanged += (s, ent) =>
                                                                   {
                                                                       if (DuplicateChanged != null) DuplicateChanged(this, IsDuplicates);
                                                                   };
            }
            e.PropertyChanged += (sender, args) =>
                                     {
                                         if (args.PropertyName.Equals("IsDuplicate"))
                                             OnPropertyChanged(new PropertyChangedEventArgs(IsDuplicatesPropertyName));
                                         else
                                         {
                                             CheckDuplicates(sender as Entity);
                                             _parent.MarkDirty();
                                         }
                                     };
        }

        void EntityCollectionEntityRemoved(object sender, EntityCollectionChangedEventArgs<T> e)
        {
            if (Items.Contains(e.Entity))
            {
                var ed = e.Entity as IDuplicateAwareEntity;
                if (ed != null)
                    ed.CheckDuplicateOnDelete();
                this.Items.Remove(e.Entity);
            }
        }

        void EntityCollectionEntityAdded(object sender, EntityCollectionChangedEventArgs<T> e)
        {
            if (!Items.Contains(e.Entity))
            {
                Items.Add(e.Entity);
                ObserveItemChanges(e.Entity);
            }
        }

        private void CheckDuplicates(Entity e)
        {
            OnPropertyChanged(new PropertyChangedEventArgs(IsDuplicatesPropertyName));
            var ed = e as IDuplicateAwareEntity;
            if (ed != null)
                foreach (var item in Items)
                {
                    ed.CheckDuplicate(item as IDuplicateAwareEntity);
                }
        }

        void ObservableEntityCollectionCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (_parent != null) _parent.MarkDirty();
            if (e.NewItems != null)
            {
                foreach (var i in e.NewItems)
                {
                    ObserveItemChanges(i as T);
                    //record it as added item only if it wasn't deleted before
                    if (!_addedItems.Contains(i as T) && !_deletedItems.Contains(i as T))
                    {
                        _addedItems.Add(i as T);
                    }
                }
            }
            if (e.OldItems != null)
            {
                foreach (var i in e.OldItems)
                {
                    var ed = i as IDuplicateAwareEntity;
                    if(ed!=null)
                        ed.CheckDuplicateOnDelete();
                    if (!_deletedItems.Contains(i as T)) _deletedItems.Add(i as T);
                }
                OnPropertyChanged(new PropertyChangedEventArgs(IsDuplicatesPropertyName));
            }
        }

        public void Undo()
        {
            foreach (var deletedItem in _deletedItems)
            {
                this.Add(deletedItem as T);
            }

            foreach (var addedItem in _addedItems)
            {
                this.Remove(addedItem as T);
            }


            //undo all changes of entities
            foreach (var item in Items)
            {                
                item.Undo();
            }
            _parent.ClearDirty();
            _deletedItems.Clear();
            foreach (var addedItem in _addedItems)
            {
                var i = addedItem as IDuplicateAwareEntity;
                if (i != null)
                    i.CheckDuplicateOnDelete();
            }
            OnPropertyChanged(new PropertyChangedEventArgs(IsDuplicatesPropertyName));
            _addedItems.Clear();
        }

        public void Commit()
        {
            foreach (var addedItem in _addedItems)
            {
                _entityCollection.Add(addedItem as T);
            }
            foreach (var deletedItem in _deletedItems)
            {
                if (_entityCollection.Contains<T>(deletedItem))
                {
                    _entityCollection.Remove(deletedItem as T);
                }
            }
        }

        public List<T> CurrentItems
        {
            get
            {
                return Items.ToList();
            }
        }

        public void Reset()
        {
            _entityCollection.ToList().ForEach(obj =>
                                                   {
                                                       var oe = obj as IObservableEntity;
                                                       if(oe!=null)
                                                           oe.CurrentEntitySet.Detach(obj);
                                                   });
           
           _addedItems.Clear();
           _deletedItems.Clear();
        }

        #region IsDuplicates Propoerty

        private const string IsDuplicatesPropertyName = "IsDuplicates";

        public bool IsDuplicates
        {
            get
            {
                return (from i in Items where i != null select i as IDuplicateAwareEntity).Any(de => de != null && de.IsDuplicate);
            }
            set
            {
                OnPropertyChanged(new PropertyChangedEventArgs(IsDuplicatesPropertyName));
            }
        }
        #endregion

        private void PrintDebugMessage()
        {
            if (typeof(T).Name.Contains("IntegrationChannel"))
            {
                Debug.WriteLine(string.Format("IsDuplicates: {0}", IsDuplicates));
            }
        }

        public string Serialize() 
        {
            var temp = this.Items.ToList();
            var serializer = new DataContractSerializer(typeof(List<T>));
            using (var ms = new MemoryStream())
            {
                serializer.WriteObject(ms, temp);
                ms.Position = 0;
                using (var sr = new StreamReader(ms))
                {
                    string result = sr.ReadToEnd();
                    return result;
                }
            }
        }

        public static Assembly CurrentAssembly
        {
            get
            {
                return Assembly.GetExecutingAssembly();
            }
        }
    }
}
